#ifndef __MMZONE_H__
#define __MMZONE_H__

#include "bootmem.h"
#include "list.h"
#include "spinlock.h"

#define MAX_ORDER   10

#define ZONE_DMA		0
#define ZONE_NORMAL		1
#define ZONE_HIGHMEM		2
#define MAX_NR_ZONES		3

/*
 * free_list: 空闲页
 * map: 位图
 * 
 * 说明
 * 位图中的每一位不是代表一个内存块，而是代表一对伙伴块,可以理解为每个位图管理的页面数为2^order * 2 = 2 ^ (1 + order)
 * 位=0: 两个伙伴块都空闲，或者都已被分配
 * 位=1: 一个伙伴块空闲，另一个已被分配
 * 
 * 伙伴系统中有两个块是"伙伴"当且仅当：
    -它们大小相同
    -它们物理地址连续
    -它们可以合并成一个更大的块
 *
 * 没有特殊说明以下所说的地址均理解为pfn
 * 对于order为n的块，两个伙伴块的地址只有第n+1位不同
 * 解释：
 * order为n：表示块大小为2^n个页面,也就是(1 << n)个页面
 * 地址对齐：order为n的块，其起始地址的低n位必须为0(1 << n 位,低n位肯定为0)
 * 
 * 使用page_idx(pfn)和order来计算在位图中的bit索引的公式为：index = page_idx >> (1 + order)
 * 
 * 示例：
 * order = 0
 * 块大小：1个页面（2^0 = 1）
 * 地址对齐：低0位为0（即没有要求，任何地址都可以）
 * 伙伴对：相邻的单个页面
 * 假设页面大小为4KB
 * 
 * 页面0: 地址 = ...0000 0000 0000 (二进制)
 * 页面1: 地址 = ...0000 0000 0001 (二进制)
 * 页面2: 地址 = ...0000 0000 0010 (二进制)
 * 页面3: 地址 = ...0000 0000 0011 (二进制)
 * 伙伴对：(0,1), (2,3), (4,5), ...
 * 页面0和1只有位0不同
 * 页面2和3只有位0不同
 * 
 * order = 2
 * 块大小：4个页面（2^2 = 4）
 * 地址对齐：低2位必须为0
 * 伙伴对：相邻的4页面块
 * 
 * 块A: 地址 = ...0000 0000 0000 (管理页面0-3)
 * 块B: 地址 = ...0000 0000 0100 (管理页面4-7)
 * 块C: 地址 = ...0000 0000 1000 (管理页面8-11)
 * 块D: 地址 = ...0000 0000 1100 (管理页面12-15)
 * 伙伴对：(A,B), (C,D), ...
 * 块A(...0000)和块B(...0100)只有位2不同
 * 块C(...1000)和块D(...1100)只有位2不同
 */
typedef struct free_area_struct
{
    struct list_head free_list;
    unsigned char     *map;
}free_area_t;

#define MAX_NR_ZONES    3 
#define NR_GFPINDEX     0x100

/*
 * 由于历史硬件限制或特性，物理内存被划分为不同的区域（Zones），每个区域有不同的用途。最常见的三个区是：
 * ZONE_DMA: 用于直接内存访问（DMA）的内存
 * ZONE_NORMAL: 可直接映射到内核地址空间的普通内存
 * ZONE_HIGHMEM: 在32位系统上，超出内核直接映射地址范围的高端内存
 * 
 * offset: 该zone在所属内存节点（pg_data_t）中的起始页面帧号（PFN）的偏移量. 例如，一个节点的内存从PFN 0x1000开始，而这个zone从0x1100开始，那么offset就是0x100
 * free_pages: 该zone中当前空闲页面的数量
 * pages_min,pages_low,pages_high:水位标记（Watermarks），用于控制内存回收和分配行为：
    -pages_min: 最低水位。低于此值，内存压力非常大，分配器会同步等待回收完成。
    -pages_low: 低水位。低于此值，kswapd开始被唤醒进行异步内存回收。
    -pages_high: 高水位。kswapd的目标是回收内存直到达到此水位
 * inactive_clean_list: 链表头，链接所有处于“非活跃且干净”状态的页面。这些页面可以被立刻回收使用
 * free_area: 伙伴系统（Buddy Allocator）的空闲链表
    -MAX_ORDER 定义了伙伴系统的最大阶数
    -free_area[0] 链接所有单个(order为0)空闲页面的page结构
    -free_area[1] 链接所有2个(order为1)连续页面的空闲块
    -... 以此类推。
 * size: 本zone所有的page数
 * zone_pgdata: 指向本zone所在的node
 * zone_start_paddr: zone的起始物理地址
 * zone_start_mapnr: zone的起始页面帧号(pfn)
 * zone_mem_map: 指向该zone的第一个page结构的指针
 */
typedef struct zone_struct
{
    spinlock_t lock;
    unsigned long offset;
    unsigned long free_pages;
    unsigned long inactive_clean_pages;
    unsigned long inactive_dirty_pages;
    unsigned long pages_min, pages_low, pages_high;

    struct list_head inactive_clean_list;
    free_area_t free_area[MAX_ORDER];

    char *name;
    unsigned long size;
    struct pglist_data *zone_pgdat;
    unsigned long		zone_start_paddr;
	unsigned long		zone_start_mapnr;
    struct page *zone_mem_map;

}zone_t;

/*
 * 为一次具体的内存分配请求，定义一个在所有可用内存区域（zones）中进行搜索的优先级顺序
 * 没有它，内存分配器会不知道从哪里开始找空闲页。是优先用自己的DMA内存？还是优先用本地节点的普通内存？还是可以去远程节点找？
 * zonelist 就是回答这个问题的“路线图”
 * 
 * zones: 指针数组，每个元素都是一个指向 zone_t 结构的指针。数组的最后以一个 NULL 指针标记结束
 * 内核分配内存时，会从 zones[0] 开始，依次检查每个 zone 是否有足够的空闲内存。一旦在某个 zone（例如 zones[2]）中分配成功，过程立即停止
 * 这个数组的顺序不是随意的，而是内核根据 gfp_mask 和系统配置（NUMA还是UMA）计算出来的。其目标有两个：
 * 1 性能优先（本地性）：优先从本地CPU节点的内存分配，避免昂贵的远程内存访问（NUMA环境下）
 * 2 合规优先：必须满足分配标志的约束。例如，GFP_DMA 请求只能从 ZONE_DMA 分配，所以它必须排在路线图的首位
 * 
 * gfp_mask: 这是一个标志位，表示此 zonelist 所服务的分配类型(GFP_KERNEL、GFP_ATMOIC、GFP_DMA等)
 * 
 * 对此结构体的一些说明：
 * 当首选的 zone 没有足够内存时，分配器不会立即失败，而是会沿着 zones[] 数组定义的路线的图，后退到下一个优先级较低的 zone 去尝试
 * 假设一个UMA系统有两个 zone：ZONE_NORMAL 和 ZONE_DMA
 * 
 * 对于 GFP_KERNEL 请求:
 * 其 zonelist 的顺序可能是：ZONE_NORMAL -> ZONE_DMA -> NULL. 分配器首先在 ZONE_NORMAL 中尝试, 如果成功，皆大欢喜
 * 如果因为空闲内存不足而失败，分配器不会直接返回失败，而是继续去 ZONE_DMA 里寻找
 * 虽然 ZONE_DMA 通常用于DMA操作，但在内存紧张时，也可以用于满足内核的普通请求。这极大地提高了分配的成功率
 * 
 * 对于 GFP_DMA 请求：
 * 其 zonelist 的顺序必须是：ZONE_DMA -> NULL
 * 因为该请求有硬件强制要求，所以分配器只能在 ZONE_DMA 中分配。如果这里没有空闲内存，分配就会立即失败，因为没有合规的后备选项。
 * 
 * NUMA系统中，zonelist 的策略更加复杂，旨在优化非统一内存访问,此处略去不表
 */
typedef struct zonelist_struct
{
    zone_t *zones[MAX_NR_ZONES + 1];
    int gfp_mask;

}zonelist_t;

/*
 * 用于描述一个内存节点（Node）
 * 非一致内存访问（NUMA)架构下每个CPU都有自己的本地内存，访问本地内存比访问其他CPU的内存更快。
 * 每个这样的内存单元就是一个节点。在简单的UMA（一致内存访问）系统中，只有一个节点。
 * node_zonelists: 分配策略，数组每个元素对应一种gfp_mask的zonelist
 * node_zones: 该节点所包含的所有内存区域（zones）的数组, 例如，一个节点可能同时包含ZONE_DMA和ZONE_NORMAL
 * node_mem_map: 指向该节点的第一个page结构的指针,类似于zone->zone_mem_map，但范围是整个节点
 * bdata: bootmem_data信息,详情可参考结构体描述
 * node_start_paddr: 该节点的起始物理地址
 * node_start_mpnr: 该节点的起始页面帧号(pfn)
 * node_size: 该节点的总大小，以页面数为单位(节点下page数量)
 * node_id: 节点id
 * node_next: 指向下一下node
 * 
 * 对node_zones和node_zonelists的解释：
 * node_zonelist里面的zones与pglist_data里的node_zones并不是一一对应的
 * pglist_data里的node_zones表示的是  本节点实际拥有的(物理上存在的)所有内存区域(zones)
 * node_zonelists里的zones可能来自本节点也可能来自其他节点
 * 
 * 假设一个双节点的NUMA系统（Node0, Node1），每个节点都有 ZONE_NORMAL 和 ZONE_DMA
 * pg_data_t node_0:
 * node_zones[0] = ZONE_DMA of Node0 (实例)
 * node_zones[1] = ZONE_NORMAL of Node0 (实例)
 * 
 * pg_data_t node_1:
 * node_zones[0] = ZONE_DMA of Node1 (实例)
 * node_zones[1] = ZONE_NORMAL of Node1 (实例)
 * 
 * zonelist_t for GFP_KERNEL on Node0:
 * zones[0] = &(ZONE_NORMAL of Node0) // 指针，指向Node0的资源
 * zones[1] = &(ZONE_DMA of Node0) // 指针，指向Node0的资源
 * zones[2] = &(ZONE_NORMAL of Node1) // 指针，指向Node1的资源！
 * zones[3] = &(ZONE_DMA of Node1) // 指针，指向Node1的资源！
 * zones[4] = NULL
 */
typedef struct pglist_data
{
    struct zone_struct node_zones[MAX_NR_ZONES];
    struct zonelist_struct node_zonelists[NR_GFPINDEX];
    struct page *node_mem_map;
    struct bootmem_data *bdata;
    unsigned long node_start_paddr;
    unsigned long node_start_mapnr;
    unsigned long node_size;

    int node_id;
    struct pglist_data *node_next;
}pg_data_t;


extern pg_data_t contig_page_data;
#define NODE_DATA(nid)		(&contig_page_data)

#define MAP_ALIGN(x)	((((x) % sizeof(mem_map_t)) == 0) ? (x) : ((x) + \
		sizeof(mem_map_t) - ((x) % sizeof(mem_map_t))))

#endif


